#include <string.h>
#include <assert.h>
#include <unistd.h>
#include "tcpclient.hpp"


tcpclient::tcpclient(uv_loop_t *ploop, std::string server_ip, uint16_t server_port, std::string local_ip, uint16_t local_port)
{
    int32_t iret = 0;

    this->serverIP = server_ip;
    this->serverPort = server_port;
    this->localIP = local_ip;
    this->localPort = local_port;
    this->loop = ploop;

    this->uvRbuf.base = new char [TCP_RECV_UVBUFF_SIZE];
    this->uvRbuf.len = TCP_RECV_UVBUFF_SIZE;
    this->rbuff = new uint8_t [TCP_RECV_BUFF_SIZE];
    this->rbuffIn = 0;
    this->rbuffOut = 0;

    this->sbuff = new uint8_t [TCP_SEND_BUFF_SIZE];
    this->sbuffIn = 0;
    this->sbuffOut = 0;
    this->repeatSendTime = 1;

    this->connectStatus = false;
    this->isReconnect = true;
    this->isClosed = false;

    do
    {
        iret = uv_mutex_init(&this->rMutex);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_mutex_init rMutex", iret);
            break;
        }

        iret = uv_mutex_init(&this->sMutex);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_mutex_init sMutex", iret);
            break;
        }

        iret = uv_timer_init(this->loop, &this->sendTimer);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_timer_init sendTimer", iret);
            break;
        }

        iret = uv_async_init(this->loop, &this->sendAsync, sTCSendAsync);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_async_init sendAsync", iret);
            break;
        }

        iret = uv_timer_init(this->loop, &this->reconnectTimer);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_timer_init reconnectTimer", iret);
            break;
        }

        iret = uv_tcp_init(this->loop, &this->clientHandle);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&this->clientHandle, 1);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_nodelay", iret);
            break;
        }

        printf("uv_tcp_nodelay\n");

        iret = uv_tcp_keepalive(&this->clientHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_keepalive", iret);
            break;
        }

        printf("uv_tcp_keepalive\n");

        iret = uv_ip4_addr(server_ip.c_str(), server_port, &this->serverAddr);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_ip4_addr serverAddr", iret);
            break;
        }

        iret = uv_ip4_addr(local_ip.c_str(), local_port, &this->localAddr);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_ip4_addr localAddr", iret);
            break;
        }

        iret = uv_tcp_bind(&this->clientHandle, (const struct sockaddr*)&this->localAddr, 0);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_bind localAddr", iret);
            break;
        }

        iret = uv_tcp_connect(&this->connectReq, &this->clientHandle, (const sockaddr*)&this->serverAddr, sTCAfterConnect);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_connect", iret);
            break;
        }

        this->sendTimer.data = this;
        this->sendAsync.data = this;

        this->clientHandle.data = this;
        this->connectReq.data = this;
        this->reconnectTimer.data = this;
    } while (0);
    
}

tcpclient::~tcpclient()
{
    delete [] this->uvRbuf.base;
    delete [] this->rbuff;
    delete [] this->sbuff;

    uv_mutex_destroy(&this->rMutex);
    uv_mutex_destroy(&this->sMutex);
}

std::string tcpclient::tcGetIP()
{
    return this->localIP;
}

int32_t tcpclient::tcGetPort()
{
    return this->localPort;
}

int32_t tcpclient::tcSendData(uint8_t *sdata, int32_t sdata_len)
{
    int32_t ret = 0;
    assert(sdata);
    assert(sdata_len > 0);

    if (this->connectStatus)
    {
        uv_mutex_lock(&this->sMutex);
        if ((this->sbuffIn + sdata_len) <= TCP_SEND_BUFF_SIZE)
        {
            memcpy(this->sbuff + this->sbuffIn, sdata, sdata_len);
            this->sbuffIn += sdata_len;

            ret = sdata_len;
        }
        uv_mutex_unlock(&this->sMutex);
    }

    return ret;
}

#if 0
int32_t tcpclient::tcRecvData(uint8_t *buff, int32_t buff_len)
{
    int32_t len = 0;

    assert(buff);
    assert(buff_len > 0);

    uv_mutex_lock(&this->rMutex);
    if (this->rbuffIn != this->rbuffOut)
    {
        if ((this->rbuffOut + buff_len) <= this->rbuffIn)
        {
            memcpy(buff, this->rbuff + this->rbuffOut, buff_len);
            len = buff_len;
            this->rbuffOut += buff_len;
            if (this->rbuffOut == this->rbuffIn)
            {
                this->rbuffIn = 0;
                this->rbuffOut = 0;
            }
        }
    }
    uv_mutex_unlock(&this->rMutex);

    return len;
}
#endif
int32_t tcpclient::tcRecvData(uint8_t *buff, int32_t buff_len)
{
    uint8_t *ptr = this->rbuff + this->rbuffOut;
    uint16_t frameLen = 0;

    assert(buff);
    assert(buff_len > 0);

    uv_mutex_lock(&this->rMutex);
    if (this->rbuffIn != this->rbuffOut)
    {
        if (((ptr[0] == 0xD2) && (ptr[1] == 0x8C)) ||
            ((ptr[0] == 0x4D) && (ptr[1] == 0x73)))
        {
            frameLen = htons(*(uint16_t *)(ptr + FRAME_OFFSET_DATALEN)) + 18;
            if ((this->rbuffOut + frameLen) <= this->rbuffIn)
            {
                memcpy(buff, ptr, frameLen);
                this->rbuffOut += frameLen;
                if (this->rbuffOut == this->rbuffIn)
                {
                    this->rbuffIn = 0;
                    this->rbuffOut = 0;
                }
            }
        }
        else
        {
            this->rbuffOut++;
        }
    }
    uv_mutex_unlock(&this->rMutex);
    return frameLen;
}

bool tcpclient::tcGetConnectStatus()
{
    return this->connectStatus;
}

void tcpclient::sTCAfterConnect(uv_connect_t* handle, int status)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    int32_t iret = 0;

    if (status)
    {
        tmpClient->connectStatus = false;
        PlayerInfo("sTCAfterConnect", status);
        tmpClient->TCStopSend();
        tmpClient->TCStartReconnect();
        uv_close((uv_handle_t *)&tmpClient->clientHandle, tcpclient::sTCAfterClientClose);
        return;
    }

    iret = uv_read_start(handle->handle, sTCAllocBufferForRecv, sTCAfterRecv);
    if (iret)
    {
        PlayerInfo("sTCAfterConnect uv_read_start", iret);
    }
    else
    {
        tmpClient->connectStatus = true;
        tmpClient->TCStartSend();
    }

    if (tmpClient->isReconnect)
    {
        tmpClient->TCStopReconnect();
    }
}

void tcpclient::sTCAllocBufferForRecv(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    assert(tmpClient);
    *buf = tmpClient->uvRbuf;
}

void tcpclient::sTCAfterRecv(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;

    if (nread > 0)
    {
        uv_mutex_lock(&tmpClient->rMutex);
        if ((tmpClient->rbuffIn + nread) <= TCP_RECV_BUFF_SIZE)
        {
            memset(tmpClient->rbuff + tmpClient->rbuffIn, 0x00, nread);
            memcpy(tmpClient->rbuff + tmpClient->rbuffIn, buf->base, nread);
            tmpClient->rbuffIn += nread;
        }
        uv_mutex_unlock(&tmpClient->rMutex);
    }
    else if (nread < 0)
    {
        PlayerInfo("sTCAfterRecv ", nread);
        tmpClient->TCStopSend();
        tmpClient->TCStartReconnect();
        uv_close((uv_handle_t *)handle, tcpclient::sTCAfterClientClose);
    }
}

void tcpclient::sTCReconnectTimer(uv_timer_t* handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    int32_t iret;

    if (!tmpClient->isReconnect)
        return;

    fprintf(stdout, "ip:%s port:%u start reconnect\n", tmpClient->localIP.c_str(), tmpClient->localPort);

    do
    {
        iret = uv_tcp_init(tmpClient->loop, &tmpClient->clientHandle);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&tmpClient->clientHandle, 1);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_nodelay", iret);
            break;
        }

        iret = uv_tcp_keepalive(&tmpClient->clientHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_keepalive", iret);
            break;
        }

        iret = uv_ip4_addr(tmpClient->serverIP.c_str(), tmpClient->serverPort, &tmpClient->serverAddr);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_ip4_addr serverAddr", iret);
            break;
        }
        
        iret = uv_ip4_addr(tmpClient->localIP.c_str(), tmpClient->localPort, &tmpClient->localAddr);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_ip4_addr localAddr", iret);
            break;
        }

        iret = uv_tcp_bind(&tmpClient->clientHandle, (const struct sockaddr*)&tmpClient->localAddr, 0);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_bind localAddr", iret);
            break;
        }

        iret = uv_tcp_connect(&tmpClient->connectReq, &tmpClient->clientHandle, (const sockaddr*)&tmpClient->serverAddr, sTCAfterConnect);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_connect", iret);
            break;
        }

        tmpClient->clientHandle.data = tmpClient;
        tmpClient->connectReq.data = tmpClient;
        tmpClient->reconnectTimer.data = tmpClient;
        return;
    } while (0);
    

    //reconnect failure, restart timer to trigger reconnect.
    // uv_timer_stop(handle);
    // tmpClient->repeatTime *= 2;
    // uv_timer_start(handle, tcpclient::sTCReconnectTimer, tmpClient->repeatTime, tmpClient->repeatTime);
    tmpClient->TCStopSend();
    tmpClient->TCStartReconnect();
    uv_close((uv_handle_t *)handle, tcpclient::sTCAfterClientClose);
}

void tcpclient::sTCAfterClientClose(uv_handle_t* handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    int32_t iret = 0;

    tmpClient->isClosed = true;
    tmpClient->connectStatus = false;
    if ((handle == (uv_handle_t *)&tmpClient->clientHandle) && tmpClient->isReconnect)
    {
        iret = uv_timer_start(&tmpClient->reconnectTimer, tcpclient::sTCReconnectTimer, tmpClient->repeatTime, 0);
        if (iret)
        {
            PlayerInfo("sTCAfterClientClose uv_timer_start", iret);
            uv_close((uv_handle_t *)&tmpClient->reconnectTimer, tcpclient::sTCAfterClientClose);
            return;
        }
    }
}

void tcpclient::sTCSendTimer(uv_timer_t* handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;

    uv_async_send(&tmpClient->sendAsync);
}

void tcpclient::sTCSendAsync(uv_async_t *handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    uv_buf_t bufs[CHUNKS_PER_WRITE];
    uv_write_t *write_reqs = &tmpClient->write_reqs;
    int32_t i = 0;

    char *ptr = (char *)tmpClient->sbuff + tmpClient->sbuffOut;
    uint32_t len = 0;

    uv_mutex_lock(&tmpClient->sMutex);
    if ((tmpClient->connectStatus) && (tmpClient->sbuffIn != tmpClient->sbuffOut))
    {
        for (i = 0; i < CHUNKS_PER_WRITE; )
        {
            if ((tmpClient->sbuffOut + TCP_SEND_UVBUFF_SIZE) <= tmpClient->sbuffIn)
            {
                len = TCP_SEND_UVBUFF_SIZE;
            }
            else
            {
                len = tmpClient->sbuffIn - tmpClient->sbuffOut;
            }

            bufs[i] = uv_buf_init(ptr, len);
            i++;

            tmpClient->sbuffOut += len;
            ptr += len;
            if (tmpClient->sbuffOut == tmpClient->sbuffIn)
            {
                tmpClient->sbuffOut = 0;
                tmpClient->sbuffIn = 0;
                break;
            }
        }

        if (i > 0)
        {
            write_reqs->data = tmpClient;
            uv_write(write_reqs, (uv_stream_t *)&tmpClient->clientHandle, bufs, i, tcpclient::sTCAfterSend);
        }
    }
    else if ((tmpClient->connectStatus) && (tmpClient->sbuffIn == tmpClient->sbuffOut))
    {
        tmpClient->TCStartSend();
    }
    uv_mutex_unlock(&tmpClient->sMutex);
}

void tcpclient::sTCAfterSend(uv_write_t* req, int status)
{
    class tcpclient *tmpClient = (class tcpclient *)req->data;

    if (status == 0)
    {
        tmpClient->TCStartSend();
    }
    else
    {
        PlayerInfo("sTCAfterSend ", status);
        tmpClient->TCStopSend();
    }
}


void tcpclient::TCStartReconnect()
{
    isReconnect = true;
    this->clientHandle.data = this;
    this->repeatTime = 1e3;
}

void tcpclient::TCStopReconnect()
{
    this->isReconnect = false;
    this->clientHandle.data = this;
    this->repeatTime = 1e3;
    uv_timer_stop(&this->reconnectTimer);
}

void tcpclient::TCStartSend()
{
    this->repeatSendTime = 10;
    uv_timer_start(&this->sendTimer, tcpclient::sTCSendTimer, this->repeatSendTime, 0);
}

void tcpclient::TCStopSend()
{
    this->repeatSendTime = 1;
    uv_timer_stop(&this->sendTimer);
}